using System;
using System.Collections.Generic;
using Gdk;
using Common;
using Cairo;

namespace GtkControls
{
    
    public class CairoDrawer : IGraphDrawer
    {
        private Size _size;
        private double _maxValue = 60.0;
        private double _fontSize = 10.0;
        public bool _drawLabels = true;

        /// <summary>Get or set the maximum value to show in the data grid.</summary>
        public double MaxValue { set { if (value > 0.0) _maxValue = value; } get { return _maxValue; } }
        
        public CairoDrawer()
        {
        }
        
        private int GetHorizontalGridNumber(Size size, double fontSize)
        {
            switch((int)(size.Height / fontSize))
            {
                case 0:
                case 1:
                    return 1;
                case 2:
                case 3:
                    return 2;
                case 4:
                    return 4;
                default:
                    return 5;
            }
        }
        
        public void DrawGraph(Context ctx, Size size, List<DataRow> data, IEnumerable<KeyValuePair<string, Cairo.Color>> labels)
        {
            _size = size;

            /* Double-buffering. First draw to image surface and then copy to screen. */
            using(Cairo.ImageSurface ims = new Cairo.ImageSurface(Cairo.Format.RGB24, size.Width, size.Height))
            using(Cairo.Context cr = new Cairo.Context(ims))
            {
                if(_drawLabels)
                {
                    using(Context labelctx = DrawLabels(size, labels))
                    {
                        CopyContextToContext(cr, labelctx, 0, 40);
                    }
                }

                DrawBackground(cr, size);
                
                foreach(var row in data)
                    DrawLine(cr, size, row);
                
                CopySurfaceToScreen(ctx, ims);
            }
        }

        private void CopyContextToContext(Context target, Context source, double x, double y)
        {
            source.Transform(new Matrix(1,0,0,1, x, y));
            Cairo.SurfacePattern sur = new Cairo.SurfacePattern(source.Target);
            sur.Filter = Cairo.Filter.Fast;
            target.SetSource(source.Target, x, ((ImageSurface)(target.Target)).Height - y); 

            target.Paint();
        }
        
        private void CopySurfaceToScreen(Context ctx, Surface surface)
        {
            Cairo.SurfacePattern sur = new Cairo.SurfacePattern(surface);
            sur.Filter = Cairo.Filter.Fast;
            ctx.Source = sur;
            ctx.Paint();
        }

        /// <summary>
        /// Draw labels to bottom of the given extrema.
        /// </summary>
        /// <returns>Width and height of the box</returns>
        public Context DrawLabels(Size size, IEnumerable<KeyValuePair<string, Cairo.Color>> labels)
        {
            Context cr = new Context(new ImageSurface(Format.RGB24, size.Width, 40));
            double x = 40.5;
            double y = 15.5;

            foreach(var label in labels)
            {
                cr.Color = label.Value;
                cr.Rectangle(x, y, 10, 10);
                cr.Fill();
                cr.Color = new Cairo.Color (1.0, 1.0, 1.0);
                cr.Rectangle(x, y, 10, 10);
                cr.LineWidth = 1.0;
                cr.Stroke();
                
                x += 20; // space for the rectangle

                cr.MoveTo(x, 25);
                cr.Color = new Cairo.Color (1.0, 1.0, 1.0);
                cr.ShowText(label.Key);

                x += cr.TextExtents(label.Key).Width;
                x += 10.0; // padding between labels
            }
            
            return cr;
        }

        public void DrawBackground(Context cr, Size size)
        {
            cr.FontOptions.HintStyle = HintStyle.Medium;
            cr.Color = new Cairo.Color(1.0, 1.0, 1.0);
            
            int numGrid = GetHorizontalGridNumber(size, _fontSize);
            double yIncrease = (size.Height - 15) / numGrid;
            
            double currentY = 0;
            for(int i=0; i < numGrid; ++i)
            {
                cr.MoveTo(0, _size.Height - currentY);
                double rate = i * MaxValue / numGrid;
                cr.ShowText(string.Format("{0} kB/s", rate));
                currentY += yIncrease;
            }
        }

        private int GetInPixels(double value)
        {
            int height = _size.Height;
            return (int)(height / (MaxValue / value));
        }
        
        public void DrawLine(Context cr, Size size, DataRow row)
        {
            const int stepX = 3;
            double x = _size.Width;

            cr.NewPath();
            cr.LineWidth = 1.5;
            cr.Color = row.Pen;
            for (int i = 0; i < row.Data.Count - 1 && x > 60; ++i)
            {
                double currentY = GetInPixels(row.Data[i]);
                
                cr.LineTo(x,  _size.Height - currentY);
                x -= stepX;
            }
            cr.Stroke();
        }
    }
}
